﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.Phone.Reactive;
using System.Xml.Serialization;

namespace SMARTMobile.WinPhone
{
    public static class DataClientService
    {
        private static readonly CookieContainer CookieContainer = new CookieContainer();

        public static IObservable<Stream> GetImage(Uri uri)
        {
            var result = (from request in Observable.Return(GetWebRequest(uri, true))
                          from response in Observable.FromAsyncPattern<WebResponse>(request.BeginGetResponse, request.EndGetResponse)()
                          select response.GetResponseStream()).ObserveOnDispatcher();
            return result;
        }

        public static IObservable<byte[]> GetContentBytes(DataSourceRequest sourceRequest)
        {
            HttpWebRequest webRequest = sourceRequest.WebRequest;
            switch (sourceRequest.WebRequest.Method)
            {
                case "POST":
                    var post = (from request in Observable.Return(webRequest)
                                from requestStream in Observable.FromAsyncPattern<Stream>(request.BeginGetRequestStream, request.EndGetRequestStream)()
                                from response in Observable.Return(WriteResponse(requestStream, sourceRequest))
                                from result in Observable.FromAsyncPattern<WebResponse>(response.BeginGetResponse, response.EndGetResponse)()
                                from item in GetBytes(result as HttpWebResponse, sourceRequest).ToObservable()
                                select item);
                    return sourceRequest.ObserveOnDispatcher ? post.ObserveOnDispatcher() : post;
                default:
                    var get = (from request in Observable.Return(webRequest)
                               from response in Observable.FromAsyncPattern<WebResponse>(request.BeginGetResponse, request.EndGetResponse)()
                               from item in GetBytes(response as HttpWebResponse, sourceRequest).ToObservable()
                               select item);
                    return sourceRequest.ObserveOnDispatcher ? get.ObserveOnDispatcher() : get;
            }
        }


        public static IObservable<T> GetItems<T>(DataSourceRequest sourceRequest) where T : class, new()
        {
            HttpWebRequest webRequest = sourceRequest.WebRequest;
            Debug.WriteLine(webRequest.RequestUri);

            switch (sourceRequest.WebRequest.Method)
            {
                case "POST":
                    var post = (from request in Observable.Return(webRequest)
                                from requestStream in Observable.FromAsyncPattern<Stream>(request.BeginGetRequestStream, request.EndGetRequestStream)()
                                from response in Observable.Return(WriteResponse(requestStream, sourceRequest))
                                from result in Observable.FromAsyncPattern<WebResponse>(response.BeginGetResponse, response.EndGetResponse)()
                                from item in GetItems<T>(GetResponseReader(result as HttpWebResponse, sourceRequest), sourceRequest.ElementName).ToObservable()
                                select item);
                    return sourceRequest.ObserveOnDispatcher ? post.ObserveOnDispatcher() : post;
                default:
                    var get = (from request in Observable.Return(webRequest)
                               from response in Observable.FromAsyncPattern<WebResponse>(request.BeginGetResponse, request.EndGetResponse)()
                               from item in GetItems<T>(GetResponseReader(response as HttpWebResponse, sourceRequest), sourceRequest.ElementName).ToObservable()
                               select item);
                    return sourceRequest.ObserveOnDispatcher ? get.ObserveOnDispatcher() : get;
            }
        }

        public static string GetResponseText(WebResponse response)
        {
            string result = response.Headers["Content-Encoding"] == "gzip"
                        ? GetResultFromGZipStream(response)
                        : GetResultFromStream(response);
            return result;
        }
        
        private static string GetResultFromGZipStream(WebResponse response)
        {
            IEnumerable<byte[]> result = GetResponseGZipBytes(response as HttpWebResponse);
            var bytes = (from b in result select b).SingleOrDefault();
            if (null == bytes) return null;
            return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
        }

        private static string GetResultFromStream(WebResponse response)
        {
            IEnumerable<byte[]> result = GetResponseBytes(response as HttpWebResponse);
            var bytes = (from b in result select b).SingleOrDefault();
            if (null == bytes) return null;
            return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
        }

        private static IEnumerable<byte[]> GetBytes(HttpWebResponse response, DataSourceRequest sourceRequest)
        {
            sourceRequest.WebResponse = response;
            var result = response.Headers["Content-Encoding"] == "gzip"
                    ? GetResponseGZipBytes(response)
                    : GetResponseBytes(response);
            return result;
        }

        private static IEnumerable<byte[]> GetResponseGZipBytes(HttpWebResponse response)
        {
            using (Stream stream = response.GetResponseStream())
            {
                ////using (var gzipStream = new GZipInputStream(stream))
                ////{
                    byte[] buffer = new byte[4096];
                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        while (true)
                        {
                            //int bytesRead = gzipStream.Read(buffer, 0, buffer.Length);
                            int bytesRead = stream.Read(buffer, 0, buffer.Length);
                            if (0 == bytesRead) return new[] { memoryStream.ToArray() };
                            memoryStream.Write(buffer, 0, bytesRead);
                        }
                    }
                ////}
            }
        }

        private static IEnumerable<byte[]> GetResponseBytes(HttpWebResponse response)
        {
            using (Stream stream = response.GetResponseStream())
            {
                byte[] buffer = new byte[4096];
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    while (true)
                    {
                        int bytesRead = stream.Read(buffer, 0, buffer.Length);
                        if (0 == bytesRead) return new[] { memoryStream.ToArray() };
                        memoryStream.Write(buffer, 0, bytesRead);
                    }
                }
            }
        }

        private static HttpWebRequest WriteResponse(Stream requestStream, DataSourceRequest request)
        {
            request.FireOnWriteResponse(requestStream);
            return request.WebRequest;
        }

        private static object GetResponseReader(HttpWebResponse response, DataSourceRequest sourceRequest)
        {
            sourceRequest.WebResponse = response;
            string types = response.ContentType.ToLower();
            if (types.Contains("html"))
            {
                InvalidContentTypeException exception = InvalidContentTypeException.Create(response);
                throw exception;
            }
            Stream stream = response.GetResponseStream();
            if (response.Headers["Content-Encoding"] == "gzip")
            {
                ////stream = new GZipInputStream(stream);
            }
            if (types.Contains("application/json"))
            {
                return new StreamReader(stream);
            }
            return XmlReader.Create(stream);
        }

        public static HttpWebRequest GetWebRequest(Uri uri)
        {
            return GetWebRequest(uri, false);
        }

        public static HttpWebRequest GetWebRequest(Uri uri, bool allowReadStreamBuffering)
        {
            Debug.WriteLine("DataClient::GetWebRequest(uri={0})", uri);
            var request = (HttpWebRequest)WebRequest.Create(uri);
            request.CookieContainer = CookieContainer;
            request.AllowReadStreamBuffering = allowReadStreamBuffering;
            return request;
        }

        private static IEnumerable<T> GetItems<T>(object reader, string elementName) where T : class, new()
        {
            return GetItems<T>((XmlReader)reader, elementName);
        }

        private static IEnumerable<T> GetItems<T>(XmlReader reader, string elementName) where T : class
        {
            XmlSerializer rssItemSerializer = new XmlSerializer(typeof(T));
            while (reader.GoToElement(elementName))
            {
                var item = rssItemSerializer.Deserialize(reader) as T;
                Debug.WriteLine(item);
                yield return item;
            }
        }
    }

    public class InvalidContentTypeException : Exception
    {
        public string Content { get; private set; }
        public string ContentType { get; private set; }

        internal static InvalidContentTypeException Create(HttpWebResponse response)
        {
            return new InvalidContentTypeException();
        }
    }
}
